package rsi.mesoscopic.model;

import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class XMLMesoscopicReader extends DefaultHandler {
	
	private NetworkModel mNetworkModel;
	
	private StringBuffer mStringBuffer;
	
	private boolean mBuildNetwork = true;
	
	private boolean mReadingNetwork = false;
	
	private int mCurrentStep = -1;
	
	private int mCurrentNodeIndex = -1;
	
	private int[] mCurrentQueue;
	
	private int[] mCurrentFlush;
	
	private int mPhaseId;
	
	private int mTime;
	
	private List<String> mLinksFrom;
	
	public XMLMesoscopicReader() {
		mStringBuffer = new StringBuffer();
		mLinksFrom = new ArrayList<String>();
	}
	
	public void setNetworkModel(NetworkModel model) {
		mNetworkModel = model;
	}
	
	public NetworkModel getNetworkModel() {
		return mNetworkModel;
	}

	@Override
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		mStringBuffer.append(ch, start, length);
	}

	@Override
	public void endDocument() throws SAXException {
	}

	@Override
	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if(qName.equalsIgnoreCase("network")) {
			mReadingNetwork = false;
		}else if(qName.equalsIgnoreCase("step")){
			mCurrentNodeIndex = -1;
		}else if(qName.equalsIgnoreCase("flush")) {
			loadFlush();
		}else if(qName.equalsIgnoreCase("queue")) {
			loadQueue();
		}else if(qName.equalsIgnoreCase("node") && !mReadingNetwork) {
			mNetworkModel.getNode(mCurrentNodeIndex).appendData(
					mCurrentQueue, mCurrentFlush, mPhaseId, mTime, mCurrentStep);
		}else if(qName.equalsIgnoreCase("phase") && mReadingNetwork) {
			
			if(mLinksFrom.size() == 0) return;
			
			checkLinks();
			Node node = mNetworkModel.getNode(mNetworkModel.nodeCount()-1);
			node.addPhase(mLinksFrom.get(0));
		}
	}

	@Override
	public void startDocument() throws SAXException {
		if(mNetworkModel == null) {
			mNetworkModel = new NetworkModel();
		}
	}

	@Override
	public void startElement(String uri, String localName, String qName,
			Attributes attributes) throws SAXException {
		
		mStringBuffer.delete(0, mStringBuffer.length());
		
		if(qName.equalsIgnoreCase("network")) {
			mReadingNetwork = true;
		}else if(mBuildNetwork && mReadingNetwork) {
			buildNewtork(uri, localName, qName, attributes);
		}else if(qName.equalsIgnoreCase("step")) {
			loadStep(attributes);
		}else if(qName.equalsIgnoreCase("node")) {
			mCurrentNodeIndex++;
		} else if(qName.equalsIgnoreCase("phase") && !mReadingNetwork) {
			mPhaseId = Integer.parseInt(attributes.getValue("id"));
		} else if(qName.equalsIgnoreCase("carsource")){
			int index = Integer.parseInt(attributes.getValue("index"));
			int amount = Integer.parseInt(attributes.getValue("amount"));
			mNetworkModel.addCarsourceAmount(index, amount);
		}
	}
	
	private void loadFlush() {
		StringTokenizer tokenizer = new StringTokenizer(mStringBuffer.toString());
		
		int[] flush = new int[8];
		
		int i = 0;
		while(tokenizer.hasMoreTokens()) {
			flush[i] = Integer.parseInt(tokenizer.nextToken());
			i++;
		}
		
		mCurrentFlush = flush;
	}
	
	private void loadQueue() {
		StringTokenizer tokenizer = new StringTokenizer(mStringBuffer.toString());
		
		int[] queue = new int[8];
		
		int i = 0;
		while(tokenizer.hasMoreTokens()) {
			queue[i] = Integer.parseInt(tokenizer.nextToken());
			i++;
		}
		
		mCurrentQueue = queue;
	}
	
	/*
	 * TODO this method was only included because this viewer doesn't support
	 * multiples sources on a phase. All links on a current phase must have
	 * the same from attribute due to limitations of mesoscopic simulator 
	 * and this interface. This check was included only to check for errors
	 */
	private void checkLinks() {
		if(mLinksFrom.size() <= 1) {
			return;
		}
		String first = mLinksFrom.get(0);
		
		for(int i = 1; i < mLinksFrom.size(); i++) {
			if(!mLinksFrom.get(i).equals(first)) {
				throw new IllegalArgumentException("Due to limitations on this " +
						" simulator, all links on a phase must have only one source.");
			}
		}
	}
	
	private void loadStep(Attributes attributes){
		mCurrentStep = Integer.parseInt(attributes.getValue("id"));
	}
	
	private void buildNewtork(String uri, String localName, String qName,
			Attributes attributes) {
		if(qName.equalsIgnoreCase("node")) {
			buildNode(attributes);
		}else if(qName.equalsIgnoreCase("edge")) {
			buildEdge(attributes);
		}else if(qName.equalsIgnoreCase("phase")) {
			mLinksFrom.clear();
		}else if(qName.equalsIgnoreCase("link")) {
			String from = attributes.getValue("from");
			mLinksFrom.add(from);
		}else if(qName.equalsIgnoreCase("carsource")) {
			String strNode = attributes.getValue("node");
			float arrivalRate = Float.parseFloat(attributes.getValue("arrivalRate"));
			mNetworkModel.addCarSource(strNode, arrivalRate);
		}
	}
	
	private void buildNode(Attributes attributes) {
		String id = attributes.getValue("id");
		int x = Integer.parseInt(attributes.getValue("x"));
		int y = Integer.parseInt(attributes.getValue("y"));
		
		Node node = new Node(id);
		node.setX(x);
		node.setY(y);
		
		mNetworkModel.addNode(node);
	}
	
	private void buildEdge(Attributes attributes) {
		String fromId    = attributes.getValue("from");
		String toId      = attributes.getValue("to");
		
		Node nodeFrom = mNetworkModel.getNodeById(fromId);
		Node nodeTo   = mNetworkModel.getNodeById(toId);
		
		nodeFrom.addOutcomingNode(nodeTo);
		nodeTo.addIncomingNode(nodeFrom);
	}
}
